﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;

namespace DDD {
    /// <summary>
    /// <see cref="Image2D"/>クラスに拡張メソッドを追加するクラス
    /// </summary>
    public static class Image2DExtensions {

        /// <summary>
        /// コンパイル
        /// </summary>
        /// <param name="img"><see cref="Image2D"/>オブジェクト</param>
        public static void Compile (this Image2D img) {
            var cache = Graphics3DExtensions.GetGLCache ();
            var id = cache[img];
            if (id > 0) {
                return;
            }
            ErrorCode err;

            id = GL.GenTexture ();
            if ((err = GL.GetError ()) > 0) {
                throw new InvalidOperationException ("glGenTexture was failed, err=" + err);
            }

            var width = img.Width;
            var height = img.Height;
            var cpp = img.Cpp;
            var internalFormat = img.PixelFormat.ToGL_InternalFormat ();
            var format = img.PixelFormat.ToGL_Format ();
            var align = img.PixelFormat.ToGL_Alignment ();
            var type = img.PixelType.ToGL ();

            GL.BindTexture (TextureTarget.Texture2D, id);

            GL.PixelStore (PixelStoreParameter.UnpackAlignment, align);
            if ((err = GL.GetError ()) > 0) {
                throw new InvalidOperationException ("glPixelStore was failed, err=" + err);
            }

      
            switch (img.PixelType) {
                case PixelType.Byte: {
                        var pixels = new byte[width * height * cpp];
                        ((Image2D<byte>)img).Get (0, 0, width, height, pixels);
                        GL.TexImage2D (TextureTarget.Texture2D, 0, internalFormat, width, height, 0, format, type, pixels);
                        break;
                    }
                case PixelType.SByte: {
                        var pixels = new sbyte[width * height * cpp];
                        ((Image2D<sbyte>)img).Get (0, 0, width, height, pixels);
                        GL.TexImage2D (TextureTarget.Texture2D, 0, internalFormat, width, height, 0, format, type, pixels);
                        break;
                    }
                case PixelType.Short: {
                        var pixels = new short[width * height * cpp];
                        ((Image2D<short>)img).Get (0, 0, width, height, pixels);
                        GL.TexImage2D (TextureTarget.Texture2D, 0, internalFormat, width, height, 0, format, type, pixels);
                        break;
                    }
                case PixelType.UShort: {
                        var pixels = new ushort[width * height * cpp];
                        ((Image2D<ushort>)img).Get (0, 0, width, height, pixels);
                        GL.TexImage2D (TextureTarget.Texture2D, 0, internalFormat, width, height, 0, format, type, pixels);
                        break;
                    }
                case PixelType.Int: {
                        var pixels = new int[width * height * cpp];
                        ((Image2D<int>)img).Get (0, 0, width, height, pixels);
                        GL.TexImage2D (TextureTarget.Texture2D, 0, internalFormat, width, height, 0, format, type, pixels);
                        break;
                    }
                case PixelType.UInt: {
                        var pixels = new uint[width * height * cpp];
                        ((Image2D<uint>)img).Get (0, 0, width, height, pixels);
                        GL.TexImage2D (TextureTarget.Texture2D, 0, internalFormat, width, height, 0, format, type, pixels);
                        break;
                    }
                case PixelType.Float: {
                        var pixels = new float[width * height * cpp];
                        ((Image2D<float>)img).Get (0, 0, width, height, pixels);
                        GL.TexImage2D (TextureTarget.Texture2D, 0, internalFormat, width, height, 0, format, type, pixels);
                        break;
                    }
                default: break;
            }
            if ((err = GL.GetError ()) > 0) {
                throw new InvalidOperationException ("glTexImage2D was failed, err=" + err);
            }

            // 実際に確保された内部フォーマットの確認
            int actualFormat;
            GL.GetTexLevelParameter (TextureTarget.Texture2D, 0, GetTextureParameter.TextureInternalFormat, out actualFormat);

            GL.GenerateMipmap (GenerateMipmapTarget.Texture2D);
            if ((err = GL.GetError ()) > 0) {
                throw new InvalidOperationException ("glGenerateMipmap was failed, err=" + err);
            }

            // 念のためデフォルトに戻す
            GL.PixelStore (PixelStoreParameter.UnpackAlignment, 4);
            GL.BindTexture (TextureTarget.Texture2D, 0);

            cache.Add (img, id);
        }

        /// <summary>
        /// レンダリング
        /// </summary>
        /// <param name="img"><see cref="Image2D"/>オブジェクト</param>
        public static void Render (this Image2D img) {
            var cache = Graphics3DExtensions.GetGLCache ();
            var id = cache[img];
            if (id == 0) {
                throw new InvalidOperationException ("OpenGL id was not found");
            }

            GL.BindTexture (TextureTarget.Texture2D, id);
        }



        /// <summary>
        /// キャプチャー
        /// </summary>
        /// <remarks>
        /// GPUからCPUにデータをコピーします。テクスチャーにレンダリングした結果をファイルに保存したい場合などに使用します。
        /// (メモ)2012/11/25 調べた限りAMDではglClampColor()が機能しない。この設定にかかわらず
        /// 固定小数では必ず0～1にクランプされ、浮動小数および整数では必ずクランプされない。
        /// </remarks>
        /// <param name="img"></param>
        public static void Capture (this Image2D img) {
            var cache = Graphics3DExtensions.GetGLCache ();
            var id = cache[img];
            if (id == 0) {
                throw new InvalidOperationException ("OpenGL id was not found");
            }

            var width = img.Width;
            var height = img.Height;
            var cpp = img.Cpp;
            var format = img.PixelFormat.ToGL_Format ();
            var align = img.PixelFormat.ToGL_Alignment ();
            var type = img.PixelType.ToGL ();
            ErrorCode err;

            GL.BindTexture (TextureTarget.Texture2D, id);

            GL.PixelStore (PixelStoreParameter.PackAlignment, align);
            if ((err = GL.GetError ()) > 0) {
                throw new InvalidOperationException ("glPixelStore was failed, err=" + err);
            }

            switch (img.PixelType) {
                case PixelType.Byte: {
                        var pixels = new byte[width * height * cpp];
                        GL.GetTexImage (TextureTarget.Texture2D, 0, format, type, pixels);
                        ((Image2D<byte>)img).Set (0, 0, width, height, pixels);
                        break;
                    }
                case PixelType.SByte: {
                        var pixels = new sbyte[width * height * cpp];
                        GL.GetTexImage (TextureTarget.Texture2D, 0, format, type, pixels);
                        ((Image2D<sbyte>)img).Set (0, 0, width, height, pixels);
                        break;
                    }
                case PixelType.Short: {
                        var pixels = new short[width * height * cpp];
                        GL.GetTexImage (TextureTarget.Texture2D, 0, format, type, pixels);
                        ((Image2D<short>)img).Set (0, 0, width, height, pixels);
                        break;
                    }
                case PixelType.UShort: {
                        var pixels = new ushort[width * height * cpp];
                        GL.GetTexImage (TextureTarget.Texture2D, 0, format, type, pixels);
                        ((Image2D<ushort>)img).Set (0, 0, width, height, pixels);
                        break;
                    }
                case PixelType.Int: {
                        var pixels = new int[width * height * cpp];
                        GL.GetTexImage (TextureTarget.Texture2D, 0, format, type, pixels);
                        ((Image2D<int>)img).Set (0, 0, width, height, pixels);
                        break;
                    }
                case PixelType.UInt: {
                        var pixels = new uint[width * height * cpp];
                        GL.GetTexImage (TextureTarget.Texture2D, 0, format, type, pixels);
                        ((Image2D<uint>)img).Set (0, 0, width, height, pixels);
                        break;
                    }
                case PixelType.Float: {
                        var pixels = new float[width * height * cpp];
                        GL.GetTexImage (TextureTarget.Texture2D, 0, format, type, pixels);
                        ((Image2D<float>)img).Set (0, 0, width, height, pixels);
                        break;
                    }
            }
            if ((err = GL.GetError ()) > 0) {
                throw new InvalidOperationException ("glGetTexImage was failed, err=" + err);
            }

            GL.PixelStore (PixelStoreParameter.PackAlignment, 4);
            GL.BindTexture (TextureTarget.Texture2D, 0);
        }

    }
}
